home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Very Best of Atari Inside
/
The Very Best of Atari Inside 1.iso
/
mint
/
mintman
/
filesys.doc
< prev
next >
Wrap
Text File
|
1991-11-02
|
28KB
|
588 lines
MiNT File Systems
MiNT allows loadable file systems, which means that it should be quite
easy to implement networked file systems, dynamically re-sizable ram
disks, or other nifty things (for example, Stephen Henson's minix.xfs
file system allows access to Minix partitions from TOS). Writing
these is not difficult, but there are a lot of data structures that
must be understood first:
File Cookies
Files and directories are represented in the kernel by "cookies".
The contents of the cookie are mostly file system dependent, i.e. the
kernel interprets only the "fs" and "dev" field of the cookie, and the
contents of the other fields may be used by a file system as it sees fit.
A file cookie has the following structure:
typedef struct f_cookie {
FILESYS *fs; /* file system that knows about this cookie */
ushort dev; /* device info (e.g. Rwabs device number) */
ushort aux; /* extra data that the file system may want */
long index; /* this+dev uniquely identifies a file */
} fcookie;
(The "FILESYS" data type is defined below.)
File System Structure
This is the structure that tells the kernel about the file system,
and gives the entry points for routines which the kernel can call in order
to manipulate files and directories. Note that actual input/output
operations are performed by a device driver; most file systems have just
one associated device driver, but some may have more. See the section on
device drivers for more information on these.
Unless otherwise specified, all of the functions should return 0 for
success and an appropriate (long negative) error code for failure.
typedef struct filesys {
struct filesys *next;
This is a link to the next file system in the kernel's list. It will be
filled in by the kernel; the file system should leave it as NULL.
long fsflags;
These flags give some information about the file system. Currently, three
flags are defined:
#define FS_KNOPARSE 0x01 /* kernel shouldn't do parsing */
#define FS_CASESENSITIVE 0x02 /* file names are case sensitive */
#define FS_NOXBIT 0x04 /* if a file can be read, it can be executed */
Other bits may be defined in future releases of MiNT; for now all other
bits in this flag should be 0.
long (*root) P_((int drv, fcookie *fc));
This is the entry point for a routine to find a file cookie for the root
directory of BIOS device "drv" (an integer in the range 0-31 inclusive).
This function is called by the kernel when initializing a drive; the kernel
will query each file system in turn for a file cookie representing the
root directory of the drive. If the file system recognizes the data on
the drive as being valid, it should fill in the cookie pointed to by "fc"
and return 0. Otherwise, it should return a negative error code (EDRIVE is
a good choice) to indicate that the drive must belong to another file
system. Note that this function is called at boot up time and also at any
time when media change is detected on a drive.
long (*lookup) P_((fcookie *dir, char *name, fcookie *fc));
Translate a file name into a cookie. "dir" is the cookie for a directory,
returned by a previous call to (*lookup) or (*root). "name" is either the
name of a file in that directory (if fsflags & FS_KNOPARSE == 0) or a path
name relative to that directory (if fsflags & FS_KNOPARSE == FS_KNOPARSE).
If the file is not found, an appropriate error code (like EFILNF) should be
returned. If the file is found, the cookie "*fc" should be filled in with
appropriate data, and either 0 or EMOUNT returned. EMOUNT should be returned
only if "name" is ".." and "dir" represents the root directory of a drive;
0 should be returned otherwise. Note that a lookup call with a null name
or with "." should always succeed and return a cookie representing the
directory itself. Also note that symbolic links should *never* be followed.
long (*creat) P_((fcookie *dir, char *name, unsigned mode,
int attrib, fcookie *fc)
Create a new file named "name" in the directory whose cookie is "dir".
"mode" gives the file's type and access permissions, as follows:
/* file types */
#define S_IFMT 0170000 /* mask to select file type */
#define S_IFCHR 0020000 /* BIOS special file */
#define S_IFDIR 0040000 /* directory file */
#define S_IFREG 0100000 /* regular file */
#define S_IFIFO 0120000 /* FIFO */
#define S_IMEM 0140000 /* memory region or process */
#define S_IFLNK 0160000 /* symbolic link */
/* file access modes for user, group, and other*/
#define S_IRUSR 0400
#define S_IWUSR 0200
#define S_IXUSR 0100
#define S_IRGRP 0040
#define S_IWGRP 0020
#define S_IXGRP 0010
#define S_IROTH 0004
#define S_IWOTH 0002
#define S_IXOTH 0001
"attrib" gives the standard TOS attribute byte.
The kernel will make the "creat" call only after using "lookup" in an attempt
to find the file. The file system should create the file (if possible) and
set the cookie pointed to by "fc" to represent the newly created file.
If an error of any sort occurs, an appropriate error number is returned,
otherwise 0 is returned.
DEVDRV *(*getdev) P_((fcookie *fc, long *devspecial))
Get the device driver which should be used to do i/o on the file whose
cookie is "fc". If an error occurs, a NULL pointer should be returned
and an error code placed in the long pointed to by "devspecial"; otherwise,
"devspecial" should be set to a device-driver specific value which will
be placed by the kernel in the "devinfo" field of the FILEPTR structure
passed to the device driver's open routine. (The interpretation of
this value is a matter for the file system and the device driver, the
kernel doesn't care.)
If the call to (*getdev) succeeds, a pointer to a device driver structure
(see below) is returned; if it fails, a NULL pointer should be returned and
an appropriate error number placed in *devspecial.
long (*getxattr) P_((fcookie *file, XATTR *xattr));
Get a file's attributes. The XATTR structure pointed to by "xattr" should
be filled in with the data for the file or directory represented by
the cookie "*file" should be filled in, and 0 returned. If a fatal
error occurs (e.g. media change) an error code is returned instead.
The XATTR structure is defined as follows:
/* structure for getxattr */
typedef struct xattr {
ushort mode;
file types and permissions; same as the mode passed to (*creat) (see above)
long index;
file index; this should be a unique number for the file, so that no
two files on the same physical drive could have the same index
ushort dev;
physical device on which the file is located; normally set to fc->dev
ushort reserved1;
set to 0
ushort nlink;
number of hard links to the file; normally 1
ushort uid;
a number representing the user that owns the file
ushort gid;
the group ownership of the file
long size;
length of the file, in bytes
short mtime, mdate;
last modification time and date of the file, in standard GEMDOS format
short atime, adate;
last access time and date for the file, in standard GEMDOS format; if the
file system does not keep separate record of these, they should be the same
as mtime and mdate
short ctime, cdate;
file creation time and date, in standard GEMDOS format; if the file system
does not keep separate record of these, they should be the same as mtime
and mdate
short attr;
TOS attribute byte for the file in the lower 8 bits; the upper 8 should
be 0
short reserved2;
reserved, set to 0
long reserved3[2];
reserved, set both long words to 0
} XATTR;
long (*chattr) P_((fcookie *file, int attr));
Change the TOS attributes of the file whose cookie is "*file" to "attr".
Only the lower 8 bits of "attr" should be considered significant, for now.
The kernel will not allow changes if the file's current attributes include
the FA_DIR bit or the FA_LABEL bit. Not all filesystems will support all
TOS attribute bits, but FA_RDONLY should probably be supported if possible;
usually setting the FA_RDONLY bit should be equivalent to turning off all
write permissions to the file.
long (*chown) P_((fcookie *file, int uid, int gid));
Change a file's user and group ownership to "uid" and "gid" respectively.
The kernel checks access permissions before making this call, so file
systems do not have to. If the file system does not support a concept
of ownership, or does not allow changes to ownership, it should return
EINVFN.
long (*chmode) P_((fcookie *file, unsigned mode));
Change a file's access permissions. "mode" is similar to the field in
the XATTR structure or the value passed to creat, except that _only_
the permission bits are significant; (mode & S_IFMT) will always be 0.
In the event that the file system supports only a subset of permissions
(e.g. the TOS file system can only control write access to the file)
then it may consider only the relevant bits of "mode".
long (*mkdir) P_((fcookie *dir, char *name, unsigned mode));
Make a new subdirectory called "name" of the directory whose cookie is
"*dir". The new directory should have the file permissions given by
"mode & ~S_IFMT". Note that the file system should do all appropriate
initializations for the new directory, including making entries for
"." and "..".
long (*rmdir) P_((fcookie *dir, char *name));
Delete the subdirectory called "name" of the directory whose cookie is
"*dir".
long (*remove) P_((fcookie *dir, char *name));
Delete the file called "name" in the directory "*dir". This call should
act like the Unix "unlink" call, i.e. if the file has more than 1 hard
link to it, only this particular link to the file should be removed and
the file contents should not be affected.
long (*getname) P_((fcookie *relto, fcookie *dir, char *pathname));
This is analogous to the "getcwd()" operation in Unix. It should get the name
of the directory whose cookie is "*dir", expressed as a path relative
to the directory whose cookie is "*relto"; normally, this is the root
directory, but the file system should not assume this. The resulting path
is placed in the array pointed to by *pathname, which is PATH_MAX bytes
long.
Example: if "*relto" is the directory "\FOO", and "*dir" is the directory
"\FOO\BAR\SUB", then after the call "pathname" should contain "\BAR\SUB".
long (*rename) P_((fcookie *olddir, char *oldname,
fcookie *newdir, char *newname));
Rename the file with name "oldname" contained in the directory whose cookie
is "*olddir" to the name "newname" in the directory whose cookie is "*newdir".
long (*opendir) P_((DIR *dirh, int tosflag));
Open a directory for reading. "dirh" is a pointer to a structure as defined
below; the file cookie for the directory being opened may be found there.
"tosflag" is a copy of "dirh->flags" and is in a sense redundant. The
file system should initialize the "fsstuff" and "index" fields of "*dirh"
to whatever it needs for carrying out a successful search.
/* structure for opendir/readdir/closedir */
typedef struct dirstruct {
fcookie fc; /* cookie for this directory */
ushort index; /* index of the current entry */
ushort flags; /* flags (e.g. tos or not) */
#define TOS_SEARCH 0x01
/* if TOS_SEARCH is set, this call originated from a TOS Fsfirst() system
* call -- if possible, the returned names should be acceptable to a
* "naive" TOS program
*/
char fsstuff[60]; /* anything else the file system wants */
} DIR;
long (*readdir) P_((DIR *dirh, char *name, int namelen, fcookie *fc));
Read the next name from the directory whose DIR structure (see above)
is "*dirh". The name should be copied into "name" (if dirh->flags & TOS_SEARCH
is nonzero) or "name+4" if dirh->flags & TOS_SEARCH is 0; in the latter case,
the first 4 bytes of "name" should be a unique index for the file. "namelen"
is the total size of the buffer for "name"; if the next file name (plus index,
if appopriate, and including the trailing 0) is too long for the buffer, as
much of it as will fit should be copied in and ENAMETOOLONG returned. If no
more file names remain unread in the directory, ENMFIL should be returned
and "name" left unchanged. Otherwise, 0 should be returned.
long (*rewinddir) P_((DIR *dirh));
Reset the file system specific fields of "*dirh" so that the next call to
"readdir" on this directory will return the first file name in the directory.
long (*closedir) P_((DIR *dirh));
Called by the kernel when the directory "*dirh" is not going to be searched
any more; if the file system needs to clean up any structures or free memory
allocated for the search it can do so here.
long (*pathconf) P_((fcookie *dir, int which));
Get path configuration information for the directory whose cookie is
"*dir". "which" indicates what kind of information should be returned,
as follows:
/* The requests for pathconf() */
#define DP_IOPEN 0 /* internal limit on # of open files */
#define DP_MAXLINKS 1 /* max number of hard links to a file */
#define DP_PATHMAX 2 /* max path name length */
#define DP_NAMEMAX 3 /* max length of an individual file name */
#define DP_ATOMIC 4 /* # of bytes that can be written atomically */
#define DP_TRUNC 5 /* file name truncation behavior */
/* possible return values for DP_TRUNC */
# define DP_NOTRUNC 0 /* long names cause an error */
# define DP_AUTOTRUNC 1 /* long names are truncated */
# define DP_DOSTRUNC 2 /* DOS 8+3 rules are used */
#define DP_MAXREQ 5 /* highest legal request */
/* Dpathconf and Sysconf return this when a value is not limited
(or is limited only by available memory) */
#define UNLIMITED 0x7fffffffL
long (*dfree) P_((fcookie *dir, long *buf));
Determine bytes used and free on the disk that the directory whose cookie
is "*dir" is contained on. "buf" points to the same kind of buffer that
the "Dfree" system call uses; see the documentation for that call.
long (*writelabel) P_((fcookie *dir, char *name));
Create a volume label with the indicated name on the drive which contains
the directory whose cookie is "*dir". If a label already exists, the file
system may either fail the call with EACCDN or re-write the label. If the
file system doesn't support the notion of labels, it should return EINVFN.
long (*readlabel) P_((fcookie *dir, char *name, int namelen));
Read the volume label for the disk which contains the directory whose cookie
is "*dir" into the buffer "name", which is "namelen" bytes long. If the
volume label (including trailing 0) won't fit, return ENAMETOOLONG.
long (*symlink) P_((fcookie *dir, char *name, char *to));
Create a symbolic link called "name" in the directory whose cookie is
"*dir". The link should contain the 0-terminated string "to". If the file
system doesn't support symbolic links, it should return EINVFN.
long (*readlink) P_((fcookie *file, char *buf, int buflen));
Read the contents of the symbolic link whose cookie is "*file" into the
buffer "buf", which is "buflen" bytes long. If the contents (including the
trailing 0) won't fit, return ENAMETOOLONG; if the file system doesn't
do symbolic links, return EINVFN.
long (*hardlink) P_((fcookie *fromdir, char *fromname,
fcookie *todir, char *toname));
Create a hard link called "toname" in the directory whose cookie is
"*todir" for the file named "fromname" in the directory whose cookie is
"*fromdir". If the file system doesn't do hard links, return EINVFN.
long (*fscntl) P_((fcookie *dir, char *name, int cmd, long arg));
Perform an operation on the file whose name is "name", in the directory with
cookie "*dir". "cmd" and "arg" specify the operation, and are file system
specific. See the documentation for Dcntl() for more details. Most file
systems will just return EINVFN for any values of "cmd" and "arg"; this call
is here so that you can provide users with a way to manipulate various special
features of your file system.
long (*dskchng) P_((int drv));
Check for media change. "drv" is the BIOS device number on which the
kernel thinks there has been a change. This function is called only
when the kernel detects what the BIOS claims is a definite disk change
(i.e. Mediach returning 2 or Mediach returning 1 and Rwabs returning -14).
This may be the result of a program trying to force a media change; if the
file system agrees that a change has occured, it should perform any
appropriate actions (e.g. invalidating buffers) and return 1; the kernel
will then invalidate any open files or directories on the device and
re-check what file system the device belongs If no change, in fact, occured,
a 0 should be returned to tell the kernel not to worry.
long zero;
A long word present to allow for future expansion; this must always be set
to 0 by file systems (for now).
} FILESYS;
typedef struct fileptr {
short links; /* number of copies of this descriptor */
ushort flags; /* file open mode and other file flags */
#define O_RWMODE 0x03 /* isolates file read/write mode */
# define O_RDONLY 0x00
# define O_WRONLY 0x01
# define O_RDWR 0x02
# define O_EXEC 0x03 /* execute file; used by kernel only */
#define O_SHMODE 0x70 /* isolates file sharing mode */
# define O_COMPAT 0x00 /* compatibility mode */
# define O_DENYRW 0x10 /* deny both read and write access */
# define O_DENYW 0x20 /* deny write access to others */
# define O_DENYR 0x30 /* deny read access to others */
# define O_DENYNONE 0x40 /* don't deny any access to others */
#define O_NOINHERIT 0x80 /* this is currently ignored by MiNT */
#define O_NDELAY 0x100 /* don't block for i/o on this file */
#define O_CREAT 0x200 /* create file if it doesn't exist */
#define O_TRUNC 0x400 /* truncate file to 0 bytes if it does exist */
#define O_EXCL 0x800 /* fail open if file exists */
#define O_BIOS 0x2000 /* file is a terminal */
#define O_HEAD 0x4000 /* file is a pseudo-terminal "master" */
The "flags" is constructed by or'ing together exactly one read/write mode,
one sharing mode, and any number of the other bits. Device drivers can
ignore the O_CREAT flag, since file creation is handled by the kernel.
The O_TRUNC flag, however, should be respected; the file should be
truncated to 0 length if this flag is set.
long pos; /* position in file */
The kernel doesn't actually use this field, except to initialize it to
0; it is recommended that device drivers that allow seeking should use it
to store the current position in the file (relative to the start of
the file). Other device drivers may use it for other purposes.
long devinfo; /* device driver specific info */
This field is passed back to the kernel from the file system from the
"getdev" call; its interpretation is file system specific, except that
if this is a terminal device (i.e. the O_BIOS bit is set in "flags") then
this must be a pointer to a struct tty for this terminal.
fcookie fc; /* file system cookie for this file */
This is the cookie for the file, as returned by the file system "lookup"
function during opening of the file.
struct devdrv *dev; /* device driver that knows how to deal with this */
This is the device driver returned by the "getdev" call.
struct fileptr *next; /* link to next fileptr for this file */
This field may be used by device drivers to keep a linked list of file
pointers that refer to the same physical file, for example, in order to
implement file sharing or locking code.
} FILEPTR;
typedef struct devdrv {
long (*open) P_((FILEPTR *f));
This routine is called by the kernel during a file "open", after it has
constructed a FILEPTR for the file being opened and determined the device
driver. The device driver should check the contents of the FILEPTR and
make any changes or initializations necessary. If for some reason the open
call should be failed, an appropriate error code must be returned (in which
case the kernel will free the FILEPTR structure automatically). For example,
if the file sharing mode in f->flags is not compatible with the sharing
mode of another open FILEPTR referring to the same physical file, EACCDN should
be returned.
long (*write) P_((FILEPTR *f, char *buf, long bytes));
Write "bytes" bytes from the buffer pointed to by "buf" to the file with
FILEPTR "f". Return the number of bytes actually written.
long (*read) P_((FILEPTR *f, char *buf, long bytes));
Read "bytes" bytes from the file with FILEPTR "f" into the buffer pointed
to by "buf". Return the number of bytes actually read.
long (*lseek) P_((FILEPTR *f, long where, int whence));
Seek to a new position in the file. "where" is the new position; "whence"
says what "where" is relative to, as follows:
/* lseek() origins */
#define SEEK_SET 0 /* from beginning of file */
#define SEEK_CUR 1 /* from current location */
#define SEEK_END 2 /* from end of file */
long (*ioctl) P_((FILEPTR *f, int mode, void *buf));
Perform a device specific function. "mode" is the function desired. All devices
should support the FIONREAD and FIONWRITE functions.
long (*datime) P_((FILEPTR *f, short *timeptr, int rwflag));
Get or set the date/time of the file. "timeptr" is a pointer to two words,
the first of which is the time and the second of which is the date.
If "rwflag" is 0, the time and date of the file should be placed into timeptr.
If "rwflag" is nonzero, then the time and date of the file should be set to
agree with the time and date pointed to by timeptr.
long (*close) P_((FILEPTR *f));
Called every time an open file is closed. Note that the file is "really"
being closed if f->links == 0, and almost all device drivers will only
want to do any processing if this is the case; otherwise, the FILEPTR is
still being used by some process.
long (*select) P_((FILEPTR *f, long proc, int mode));
Called by Fselect() when "f" is one of the file handles a user has chosen to
do a select on. If mode is O_RDONLY, the select is for reading; if
it is O_WRONLY, it is for writing (if it is for both reading and writing,
the function will be called twice). The select function should return
1 if the device is ready for reading or writing (i.e. if a read or write
call to the device will not block); otherwise, it should take whatever
steps are necessary to arrange to wake up the process whose PROC structure is
pointed to by "proc" when the appropriate I/O on the device becomes possible.
Normally, this will be done by calling the "wakeselect" function as passed
by the kernel in "struct kerinfo" with "proc" as its parameter.
void (*unselect) P_((FILEPTR *f, long proc, int mode));
Called when the kernel is returning from an Fselect that had previously
selected this file or device; the device driver should no longer notify
"proc" when I/O is possible for this file or device. "mode" is the same
mode as was passed to the select() function (see above), i.e. either
O_RDONLY or O_WRONLY; as with select(), unselect() will be called twice
if both input and output were selected for.
} DEVDRV;
How the File System Is Booted
A loadable file system is an ordinary TOS executable file with an
extension of ".xfs". MiNT searches its current directory (normally the
root directory of the boot disk) when it is starting for all such
files, and loads them with Pexec mode 3. It then does a jump to
subroutine call to the first instruction of the loaded program,
passing on the stack a pointer to a structure of type "struct kerinfo"
(see below) which describes the version of MiNT and provides entry points
for various utility functions.
The file system should *not* set up a stack or shrink its basepage
(so the ordinary C startup code is not necessary). MiNT has already provided
a stack of about 8K or so, and has shrunk the basepage to the bare minimum.
What the file system initialization code *should* do is to check that an
appropriate version of MiNT is running and to otherwise check the system
configuration to see if it is appropriate for the file system driver.
If so, a pointer to a FILESYS structure should be returned; if not, a NULL
pointer should be returned.
Note that it is not necessary to actually check during initialization for
the presence of disks with the appropriate file system types; MiNT will
call the file system "root" function for each drive in the system, and such
checks should be done there.
If the file system driver wishes to add new (pseudo) drives to the system,
it should update the drive configuration variable stored at 0x4c2 to
reflect the presence of these new drives.
File system drivers should *not* make any calls to the BIOS or GEMDOS
directly; all such calls should be made through the vectors provided by
the kernel as part of the struct kerinfo.
/*
* this is the structure passed to loaded file systems to tell them
* about the kernel
*/
typedef long (*Func)();
struct kerinfo {
short maj_version; /* kernel version number */
short min_version; /* minor kernel version number */
ushort default_mode; /* default file access permissions */
short reserved1; /* room for expansion */
/* OS functions */
Func *bios_tab; /* pointer to the BIOS entry points */
Func *dos_tab; /* pointer to the GEMDOS entry points */
/* media change vector: call this if a device driver detects a disk
* change during a read or write operation. The parameter is the BIOS device
* number of the disk that changed.
*/
void (*drvchng) P_((unsigned));
/* Debugging stuff */
void (*trace) P_((char *, ...)); /* informational messages */
void (*debug) P_((char *, ...)); /* error messages */
void (*alert) P_((char *, ...)); /* really serious errors */
void (*fatal) P_((char *, ...)); /* fatal errors */
/* memory allocation functions */
/* kmalloc and kfree should be used for most purposes, and act like malloc
* and free respectively. umalloc and ufree may be used to allocate/free memory
* that is attached to the current process, and which is freed automatically
* when the process exits; this is generally not of much use to a file system
* driver
*/
void * (*kmalloc) P_((long));
void (*kfree) P_((void *));
void * (*umalloc) P_((long));
void (*ufree) P_((void *));
/* utility functions for string manipulation */
int (*strnicmp) P_((char *, char *, int));
int (*stricmp) P_((char *, char *));
char * (*strlwr) P_((char *));
char * (*strupr) P_((char *));
int (*sprintf) P_((char *, const char *, ...));
/* utility functions for manipulating time */
/* convert "ms" milliseconds into a DOS time (in td[0]) and date (in td[1]) */
void (*millis_time) P_((unsigned long ms, short *td));
/* convert a DOS style time and date into a Unix style time; returns the
* Unix time
*/
long (*unixtim) P_((unsigned time, unsigned date));
/* convert a Unix time into a DOS time (in the high word of the returned
* value) and date (in the low word)
*/
long (*dostim) P_((long));
/* utility functions for dealing with pauses */
/* go to sleep temporarily for at least "n" milliseconds */
void (*nap) P_((unsigned n));
/* wait on system queue "que" until a condition occurs */
void (*sleep) P_((int que, long cond));
/* wake all processes on queue "que" that are waiting for condition "cond" */
void (*wake) P_((int que, long cond));
/* wake a process that is doing a select(); "param" should be the process
* code passed to select()
*/
void (*wakeselect) P_((long param));
/* file system utility functions */
/* "list" is a list of open files; "f" is a new file that is being opened.
* If the file sharing mode of "f" conflicts with any of the FILEPTRs
* in the list, then this returns 1, otherwise 0.
*/
int (*denyshare) P_((FILEPTR *list, FILEPTR *f));
/* reserved for future use */
long res2[10];
};